package com.verisette.ncos.service.accesscontrol.bizunit;

import java.security.NoSuchAlgorithmException;
import java.sql.Timestamp;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.log4j.Logger;
import org.corp.agi.common.constant.CommonConstant;
import org.corp.agi.common.exception.BusinessException;
import org.corp.agi.common.exception.ProcessException;
import org.corp.agi.common.exception.TechnicalException;
import org.corp.agi.service.bean.ProcessContext;
import org.corp.agi.service.bean.UserBean;
import org.corp.agi.service.bizunit.BusinessUnit;
import org.corp.agi.service.bizunit.CommonBusinessUnit;
import org.corp.common.util.security.Encrypter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Required;

import com.verisette.ncos.common.ProjectConstant;
import com.verisette.ncos.persistence.dao.AccessControlDAO;
import com.verisette.ncos.persistence.domain.LogPwdChange;
import com.verisette.ncos.persistence.domain.MasterUserProfile;

/**
 * {Description here}
 *
 * @author Thippawan Muangchu (Tip)
 * @since Oct 4, 2010 3:43:57 PM
 * @version 1.0.0
 * @see 
 */
public class ChangePasswordBU extends CommonBusinessUnit {
	
	private Logger logger = Logger.getLogger(ChangePasswordBU.class);
	
	// Local variable
	private String encryptNewPassword;
	
	private Encrypter encrypter;	
	@Autowired
	public void setEncrypter(Encrypter encrypter) {
		this.encrypter = encrypter;
	}
	
	private AccessControlDAO accessControlDAO;
	@Autowired
	@Required
	public void setAccessControlDAO(AccessControlDAO accessControlDAO) {
		this.accessControlDAO = accessControlDAO;
	}

	private BusinessUnit insertAccessLogBU;
	@Autowired
	@Required
	public void setInsertAccessLogBU(BusinessUnit insertAccessLogBU) {
		this.insertAccessLogBU = insertAccessLogBU;
	}
	
	private BusinessUnit updateAccessLogBU;
	@Autowired
	@Required
	public void setUpdateAccessLogBU(BusinessUnit updateAccessLogBU) {
		this.updateAccessLogBU = updateAccessLogBU;
	}
		
	@Override
	public void execute(ProcessContext processContext) throws ProcessException {
		
		Map<String, Object> inputMap = (Map<String, Object>)processContext.getValueObject();
		
		UserBean loginUser = (UserBean) inputMap.get(ProjectConstant.USER_PROFILE);
		String ipAddress = (String)inputMap.get(ProjectConstant.USER_PROFILE_IP_ADDRESS);
		String oldPassword = (String)inputMap.get(ProjectConstant.CHANGE_PASSWORD_OLD_PASSWORD);
		String newPassword = (String)inputMap.get(ProjectConstant.CHANGE_PASSWORD_NEW_PASSWORD);
		String confirmPassword = (String)inputMap.get(ProjectConstant.CHANGE_PASSWORD_CONFIRM_PASSWORD);
		
		
		//  Make sure lowercase loginname
		loginUser.setUsername( loginUser.getUsername().toLowerCase() );
						
		String encryptPassword;
		try {
			if( encrypter != null ){
				encryptPassword = encrypter.encrypt(oldPassword);	
				encryptNewPassword = encrypter.encrypt(newPassword);	
			}
			else{ //If no encrypter is configed -> use plain password 
				encryptPassword = oldPassword;
				encryptNewPassword = newPassword;
			}
			
			loginUser.setPassword( encryptPassword );
			
		} catch (NoSuchAlgorithmException e) {
			logger.error("Error while encrypting password", e);
			throw new TechnicalException( CommonConstant.MESSAGE_CODE_AGI0001, e);
		}

		

		//  (1) Check username is correct
		List<MasterUserProfile> masUsers = (List<MasterUserProfile>) commonDAO.findByProperty(MasterUserProfile.class, "uspUserName", loginUser.getUsername()); 	
		if( masUsers == null || masUsers.size() != 1 ){
			logger.info("User ["+loginUser.getUsername()+"] is not found.");	//User is not found
			throw new BusinessException(ProjectConstant.MESSAGE_CODE_ACC0003);
		}
		
		
		//................................................................//
		
		
		//  (2) Check old password
		MasterUserProfile authenUser = (MasterUserProfile) masUsers.get(0);

		if( !loginUser.getPassword().equals( authenUser.getUspPassword() ) ){
			logger.info("Old Password for user ["+loginUser.getUsername()+"] is invalid.");
			logger.info("Input incorrect old password.");
			// Check incorrect old password > 3 times
			int wrongLoginCounter = authenUser.getUspWrongLoginCounter()+1;
			if( wrongLoginCounter > Integer.parseInt(ProjectConstant.MAXIMUM_WRONG_LOGIN_COUNTER) ){
				// Call insert access log BU (Account is locked)
				Map<String, Object> inputAccessLogMap = new HashMap<String, Object>();
				inputAccessLogMap.put(ProjectConstant.ACCESS_LOG_MESSAGE, ProjectConstant.ACCESS_LOG_ACCOUNT_LOCKED);
				inputAccessLogMap.put(ProjectConstant.USER_PROFILE_IP_ADDRESS, ipAddress);
				inputAccessLogMap.put(ProjectConstant.USER_PROFILE, authenUser);
				processContext.setValueObject(inputAccessLogMap);
				insertAccessLogBU.execute(processContext);
				
				logger.info("Account is locked.");
				throw new BusinessException(ProjectConstant.MESSAGE_CODE_ACC0011);
				
			}else{
				
				// Call update wrong login counter
				processContext.setValueObject(authenUser);
				updateAccessLogBU.execute(processContext);
				
				// Call insert access log BU (Input incorrect password)
				Map<String, Object> inputAccessLogMap = new HashMap<String, Object>();
				inputAccessLogMap.put(ProjectConstant.ACCESS_LOG_MESSAGE, ProjectConstant.ACCESS_LOG_INPUT_INCORRECT_PASSWORD);
				inputAccessLogMap.put(ProjectConstant.USER_PROFILE_IP_ADDRESS, ipAddress);
				inputAccessLogMap.put(ProjectConstant.USER_PROFILE, authenUser);
				processContext.setValueObject(inputAccessLogMap);
				insertAccessLogBU.execute(processContext);
				
				logger.info("Input incorrect old password.");
				throw new BusinessException(ProjectConstant.MESSAGE_CODE_ACC0008);	
			}
		} 
		
		
		//................................................................//
		
		
		//  (3) Check confirm password equal new password
		if( !confirmPassword.equals( newPassword ) ){
			logger.info("Confirm Password are not equal with New Password.");
			throw new BusinessException(ProjectConstant.MESSAGE_CODE_ACC0009);
		} 
		
		
		//................................................................//
		
		
		//  (4) Check password policy validation result
		if(this.validatePwdRex(newPassword)==false) {
			logger.info("Password policy validation result is fail.");
			throw new BusinessException(ProjectConstant.MESSAGE_CODE_ACC0010);
		}
		
		if(this.checkPwdDuplicateInFiveTimes(encryptNewPassword, String.valueOf(authenUser.getUspId()))) {
			logger.info("Password duplicate.");
			throw new BusinessException(ProjectConstant.MESSAGE_CODE_ACC0010);
		}
		
		
		//Calculate next expire date
		Calendar cal = Calendar.getInstance();
		cal.add(Calendar.DAY_OF_MONTH, Integer.parseInt(ProjectConstant.EXPIRE_DAY));
		authenUser.setUspExpireDate(new Timestamp(cal.getTimeInMillis()));
		
		logger.debug("Update new password.");
		this.updateNewPassword(encryptNewPassword, authenUser);	// update new password
		
		logger.debug("Insert Log Password Change.");
		this.insertLogPwdChg(authenUser);
		
		logger.debug("Insert access log: change password complete.");
		// Call insert access log BU (Change password complete)
		Map<String, Object> inputAccessLogMap = new HashMap<String, Object>();
		inputAccessLogMap.put(ProjectConstant.ACCESS_LOG_MESSAGE, ProjectConstant.ACCESS_LOG_CHANGE_PWD_COMPLETE);
		inputAccessLogMap.put(ProjectConstant.USER_PROFILE_IP_ADDRESS, ipAddress);
		inputAccessLogMap.put(ProjectConstant.USER_PROFILE, authenUser);
		processContext.setValueObject(inputAccessLogMap);
		insertAccessLogBU.execute(processContext);
		
		logger.debug("Reset login fail counter.");
		this.resetLoginFailCounter( authenUser );	// reset login fail counter
		
		processContext.setValueObject(authenUser);
		processContext.addMessage(ProjectConstant.MESSAGE_CODE_ACC0001);
	}
	
	
	/**
	 * Validate password with regular expression
	 * @param password password for validation
	 * @return true valid password, false invalid password
	*/
	private boolean validatePwdRex(String password){
		boolean result = false;
		Pattern pattern = Pattern.compile(ProjectConstant.PASSWORD_PATTERN);
		Matcher matcher = pattern.matcher(password);
		result = matcher.matches();
		return result;
	}
	
	private boolean checkPwdDuplicateInFiveTimes(String encryptPwd, String uspId){
		boolean result = false;
		int destination = 0;
		int source = 0;
		destination = accessControlDAO.getDestinationLogPasswordChange(uspId);
		source = accessControlDAO.getSourceLogPasswordChange(encryptPwd, uspId, destination);
		
		if( source!=0 ){
			result = true;
		}

		return result;
	}
	

	// Update new password
	private void updateNewPassword(String newPasswordEncrypted, MasterUserProfile userProfile){
		userProfile.setUspPassword(newPasswordEncrypted);
		//add by Rattapol S.
		userProfile.setUspForceChngPwdFlag("N");
		commonDAO.update(userProfile);
	}
	
	// Insert Log Password Change
	private void insertLogPwdChg(MasterUserProfile userProfile){
		Number runningNumber = (Number)commonDAO.nativeQuerySingleResult("select MAX(RUNNING_NUMBER) from LOG_PWD_CHANGE where usp_Id = ?", userProfile.getUspId());
		runningNumber = (runningNumber==null)?0:runningNumber;
		LogPwdChange logPwdChg = new LogPwdChange();
		logPwdChg.setUspId(userProfile.getUspId());
		logPwdChg.setChangePassword(encryptNewPassword);
		logPwdChg.setRunningNumber(runningNumber.intValue()+1);
		logPwdChg.setPwdChangeDate(new Timestamp(new Date().getTime()));
		commonDAO.save(logPwdChg);
	}

	
	// Reset login fail counter
	private void resetLoginFailCounter(MasterUserProfile authenUser){
		authenUser.setUspWrongLoginCounter( Short.parseShort(ProjectConstant.DEFAULT_WRONG_LOGIN_COUNTER) );
		commonDAO.update(authenUser);
	}
	
}
